home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-10-22 | 19.6 KB | 490 lines | [TEXT/MPS ] |
- Zippo QuickStart:
-
- Step 1 is to build the libraries. If you have not built the libraries, please see either the
- “==MPW read me” or “==THINK read me” file.
-
- Getting started with the tutorial:
-
- Step 1) Copy the Zippo project folder.
- Step 2) If you are using MPW, you may wish to delete the files “Zippo.π” and “Zippo.π.rsrc”.
- If you are using THINK, you may wish to delete the file “Zippo.make”.
- Step 3) You may wish to change the name of the project from Zippo. Change the file name
- for those files names Zippo-something. This tutorial will assume that you have
- left the project names Zippo.
- Step 4) Build Zippo. If you changed the project name, and you are using MPW, you will have
- to change the “AppName” in the make file.
- Step 5) Run Zippo. Note that the “File” and “Edit” menus are already implemented.
- (The rest of the menus are your problem.)
-
- So, you now have the standard Zippo running. Let's change it.
-
- The most noticeable change you can make is to get something to draw in the window. The code
- for drawing in the window is in the file “Window2.c”. The “2” in the name suggests that there
- is another file containing window source code. This is the case. There is a file called
- “Window.c” in the library. What this suggests is that windowing responsibilities are split
- between the library and the application, which is also the case.
-
- You will note that you have another file in Zippo named this way. Filing responsibilities
- are also split between the application and the library, and hence you have a source file
- called “File2.c”.
-
- So, let's open the source file “Window2.c” and take a look. A quick way to see what this
- source file is about is to search for •• marks. The functions that have •• in the comments
- are automatically called by the library's framework code. All you need to do is to drop
- some code into these functions, and the framework calls it at the right times.
-
- Sounds good. Does it work? Let's try it.
-
- Find the function “ImageDocument” and add some code to it so it looks like this:
-
- OSErr ImageDocument(FileRecHndl frHndl)
- {
- #pragma unused (frHndl)
-
- Rect rct;
-
- SetRect(&rct, 10, 10, 300, 200);
- FrameRect(&rct);
- return(noErr);
- }
-
-
- Build it and run it. A simple rect shows up in the window. We can note some interesting
- behaviors at this point. Note that the zoom box zooms to the size that the window already
- is. That is because the window is zoomed to the document size. The default document size
- is the size of the window based on the 'WIND' resource for the window (number 128).
-
- We can change this in two ways:
-
- 1) We can change the resource 'WIND', ID#128.
- 2) We can change the code to override the default document size.
-
- Method 1 is too simple, so let's do method 2.
-
- At the point in the document where ImageDocument is called, it is too late to change the
- size of the window. However, we could change the document size at this point. Even
- though ImageDocument isn't the place to do this, let's change it there anyway.
-
- There is a library call to change the document size. It records the new size, plus it
- adjusts the scrollbar max values to reflect that more area can now be scrolled to.
- Here's the prototype for the call:
-
- void SetDocSize(FileRecHndl frHndl, long hSize, long vSize);
-
- Just pass in the file record handle (frHndl), and the new horizontal and vertical size
- of the document.
-
- ImageDocument is passed in the frHndl of the document to be imaged, so all we have to do
- is to pass it to SetDocSize.
-
- NOTE: For MPW users, passing frHndl to SetDocSize means that frHndl is no longer not used.
- You probably want to remove the #pragma unused when you add this code.
-
-
- So now ImageDocument might look like this:
-
- OSErr ImageDocument(FileRecHndl frHndl)
- {
- Rect rct;
-
- SetDocSize(frHndl, 2000, 2000); /* Make document plenty big. */
- SetRect(&rct, 10, 10, 300, 200);
- FrameRect(&rct);
- return(noErr);
- }
-
- Now the zoom actually changes the size of the window. However, it becomes apparent that
- there is something wrong when the window is zoomed, compared to how the window initially
- comes up. When the window initially comes up, the scrollbars are still inactive, but
- when the window is zoomed, they become active. They should be active all of the time, since
- we set the document size substantially greater than the viewable part of the window.
-
- So, what's the deal?
-
- The deal is that the scrollbars are active when the window initially comes up. They just
- don't look it. Try creating a new window and then start scrolling right away. The scrollbars
- do actually work. Well, this is a bug, isn't it?
-
- Yep. It's a bug. The bug is that SetDocSize didn't redisplay the scrollbars when we called
- it. Actually SetDocSize is fine. The problem is that the scrollbars are clipped out of the
- drawing area when we are in ImageDocument. This is actually good, as we don't want the
- content of the window drawing over our scrollbars. The library first removes the frame area
- from the drawable portion of a window, and then the library calls ImageDocument.
-
- What this means is that the SetDocSize call doesn't belong in the ImageDocument function.
- (I said it wasn't a good place for it.) Well, where does it belong? You could place the
- call in the function InitContent. InitContent is called after the window is created, and
- you now want to initialize the content of the window. Moving the SetDocSize call to here
- will make this work quite nicely. Try it. Put the ImageDocument function back to the way
- it was prior to our adding the SetDocSize call to it, and place the SetDocSize call in the
- “Window2.c” function InitContent. (MPW users have to contend with “#pragma unused” statements.)
- The InitContent function would look like this"
-
- OSErr InitContent(FileRecHndl frHndl, WindowPtr window)
- {
- #pragma unused (window)
-
- SetDocSize(frHndl, 2000, 2000); /* Make document plenty big. */
- return(noErr);
- }
-
- Now the scrollbars appear initially correct. Note that the initial window size hasn't changed.
- As a point of clarification, I should state that InitContent is called only once per window.
- It is only called when a window is getting created. ImageDocument gets called every time the
- window needs redrawing (or when the user is printing, but more on this later).
-
- What if we wanted the window to initially open to reflect the size of the document? Of course
- a document as large as 2000,2000 isn't going to fit on many monitors, but how about trying to
- open the document this big, not to exceed the size of the monitor. What this means is setting
- the document size from within the InitContent function is too late. The window has already
- been created and sized, and InitContent is being called to make changes to the content of a
- window, not the window itself.
-
- So, if InitContent is too late, where isn't it too late?
-
- Here's how you work with documents and frameworks. You first try to create a document,
- (an frHndl), and if you succeed, you then give the document a window. You can do stuff
- between creating a document and giving the document a window. The user doesn't see the
- actual document. They just see the window you give the document, so until you give the
- document a window, the user will see nothing. This is where you would want to set the
- document size.
-
- Since you want to set the document size prior to having a window, it is reasonable to
- expect that you will have to add code to some file other than the “Window2.c” file.
- Again, this is the case. You want to add code to the file “File2.c”. The function that
- you want to add code to is called InitDocument.
-
- InitDocument looks like this:
-
-
- OSErr InitDocument(FileRecHndl frHndl)
- {
- OSErr err;
-
- err = noErr;
-
- switch ((*frHndl)->fileState.sfType) {
- case kDocFileType:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- /* Any additional document initialization could go here. */
- }
- break;
- #if VH_VERSION
- case kViewHierFileType:
- return(VHInitDocument(frHndl));
- break;
- #endif
- }
-
- return(err);
- }
-
- InitDocument is given a file reference (frHndl). The frHndl contains a bunch of file
- information, one part of which is the file type. The switch statement gets this type
- from the frHndl and then the case statements do the appropriate thing based on that type.
-
- You don't have to worry about the kViewHierFileType case statement. That is there so that
- the View Hierarchy debugging facility is available. Discarding this file type, there is
- only one file type left, kDocFileType. If you have only one type of document in your
- application, this is all you will ever need. If you add document types to your application,
- you will end up with more case statements here.
-
- You want to replace the comment “Any additional document...” with your code, so that the
- function looks like this:
-
- OSErr InitDocument(FileRecHndl frHndl)
- {
- OSErr err;
-
- err = noErr;
-
- switch ((*frHndl)->fileState.sfType) {
- case kDocFileType:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- SetDocSize(frHndl, 2000, 2000);
- }
- break;
- #if VH_VERSION
- case kViewHierFileType:
- return(VHInitDocument(frHndl));
- break;
- #endif
- }
-
- return(err);
- }
-
-
- Don't forget to yank the SetDocSize call out of InitContent, as we have taken care of
- setting the document size elsewhere and don't need it anymore.
-
-
- So that was reasonable painless. What else can we easily do?
-
- How about adding a ruler to our window?
-
- Since we are in the function InitDocument, let's set another document attribute. Let's set
- the topSidebar to a non-zero value and see what happens. Add the following lines next to the
- SetDocSize call:
-
- SetSidebarSize(frHndl, kwNoChange, 40);
- /* Don't change the left sidebar (leave it 0), but set the top sidebar to 40. */
-
- Now when you run the application, you see that the vertical scrollbar doesn't go all the way
- to the top anymore. There's a 40 pixel gap at the top of the window. Try scrolling the rect
- around the window. Note that it is clipped out of this area. This area shouldn't scroll with
- with document, as we want to use it as a ruler. All we need to do now is to write some code to
- draw the ruler.
-
- So, where does our ruler drawing code go? It goes in a function called DrawFrame. DrawFrame
- is found in the “Window2.c” file. It is also called automatically by the framework when the
- frame needs redrawing. I consider it part of the frame, just like the scrollbars and grow
- icon. The frame portion of the window can't be drawn over by the ImageDocument procedure.
- This is why the rect gets scrolled behind this top area.
-
- Here's some quick-and-ugly ruler code that we can use:
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window)
- {
- short i;
-
- MoveTo(0, 39); /* Draw a bottom line for the ruler bottom. */
- Line(20000, 0); /* Make it plenty long. */
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54, 0);
- Line(0, 4);
- }
- }
-
- We could add text to show the inches and stuff, but this is just a tutorial, so the heck with it.
-
- Now, let's run the application and note a problem. If we scroll vertically, everything works
- just fine, but if we scroll horizontally, we should really scroll the ruler, too.
-
- No problem. The framework calls us at the right time. The function ScrollFrame is called
- when the document is scrolled. Often you will do nothing, as you may not even have rulers
- in your document, but here we do care.
-
- There are a couple of ways we could address this. One is to use ScrollRect to scroll the
- ruler the right amount, and then call DrawFrame. Here's some code to do this:
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- WindowPtr oldPort;
- Rect rct;
- RgnHandle rgn;
-
- GetPort(&oldPort);
- SetPort(window);
- SetOrigin(0, 0);
-
- rct = window->portRect; /* Get the window's portRect, so we can scroll the top (ruler). */
- rct.bottom = 39; /* Not 40, as there is no need to scroll the bottom line. */
- rgn = NewRgn();
- ScrollRect(&rct, dh, 0, rgn); /* Only scroll horizontally. */
- DisposeRgn(rgn);
- DrawFrame(frHndl, window);
-
- SetPort(oldPort);
- }
-
-
- The only problem is that the code we wrote for DrawFrame assumed an origin of 0,0. Since
- the ruler can now scroll, we have to take this into account. Here's the new DrawFrame
- code that takes the horizontal origin into account:
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window)
- {
- Point org;
- short i;
-
- GetContentOrigin(window, &org);
- SetOrigin(org.h, 0);
-
- MoveTo(0, 39);
- Line(20000, 0);
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i, 0);
- Line(0, 16);
-
- MoveTo(72 * i + 36, 0);
- Line(0, 8);
-
- MoveTo(72 * i + 18, 0);
- Line(0, 4);
- MoveTo(72 * i + 54, 0);
- Line(0, 4);
- }
-
- SetOrigin(0, 0);
- }
-
-
- Note that it restores the origin to 0,0 before it leaves. That's the rule. The framework
- expects it to be 0,0, so please make it so when you leave.
-
-
- So that covers how to scroll the ruler using the toolbox call ScrollRect. Here's a way to
- scroll the ruler without calling ScrollRect. We first change ScrollFrame to simply
- call DrawFrame, and then we let DrawFrame do all the work.
-
- We can use the offscreen drawing package GWLayers to first draw the ruler offscreen, and
- then transfer it to the window. Here's the DrawFrame code to do this:
-
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window)
- {
- WindowPtr oldPort;
- Point oldOrg, contOrg;
- LayerObj windowLayer, rulerLayer;
- Rect rct;
- short i;
-
- GetPort(&oldPort); /* The framework sets the port for us, and it sets */
- SetPort(window); /* the origin to 0,0, but now ScrollFrame can call */
- oldOrg.h = window->portRect.left; /* here, so do the port saving and origin stuff, */
- oldOrg.v = window->portRect.top; /* since we don't know where we were called from. */
- /* The upper-left of the window's portRect is the */
- /* origin, so by saving those, we will be able to */
- /* set the origin back to whatever it was before. */
-
- GetContentOrigin(window, &contOrg); /* Find out where the document is scrolled. */
- SetOrigin(contOrg.h, 0); /* Set the window's horizontal origin to that. */
-
- NewLayer(&windowLayer, nil, nil, window, 0, 0L); /* We are using GWLayers to draw offscreen. */
- rct = window->portRect; /* The NewLayer call created a layer object */
- rct.bottom = 40; /* for the whole window. We only want the */
- (*windowLayer)->dstRect = rct; /* top 40 pixels, so set the dstRect to */
- /* just the top portion of the window. */
-
- NewLayer(&rulerLayer, windowLayer, nil, nil, 0, 0L);
- SetLayerWorld(rulerLayer);
- /* We may not have enough memory to create the offscreen GWorld, so this call may fail,
- ** but that's actually okay. If NewLayer fails, it returns nil for rulerLayer. If
- ** we pass nil into SetLayerWorld, it does nothing. If it does nothing, then the port
- ** is still set to the last port, which is the document's window (SetPort above).
- ** This means that in the worst case, the ruler will flicker when it is redrawn.
- ** We will not blow up. */
-
- EraseRect(&rct);
- /* Erase the offscreen GWorld (or possibly the ruler in the window if low on ram. */
-
- MoveTo(0, 39); /* Draw a line at the bottom of the ruler that's long enough. */
- Line(20000, 0);
-
- MoveTo(0, 39); /* Draw a bottom line for the ruler bottom. */
- Line(20000, 0); /* Make it plenty long. */
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54, 0);
- Line(0, 4);
- }
-
- InvalLayer(windowLayer, GetEffectiveDstRect(windowLayer), false);
- /* Invalidate the entire layer so it all transfers to the window. */
-
- UpdateLayer(windowLayer);
- /* Transfer all invalid areas. */
-
- ResetLayerWorld(rulerLayer);
- /* Undo the above SetLayerWorld. */
-
- DisposeThisAndBelowLayers(windowLayer);
- /* Dispose of all the layers we created in thif function. */
-
- SetOrigin(oldOrg.h, oldOrg.v); /* Put origin and port back the way they were. */
- SetPort(oldPort);
- }
-
-
- The first method seems simpler, but for certain types of rulers, you may wish to use this
- technique. The first method first does a ScrollRect, and then it redraws. The ScrollRect
- erases the area that scrolled into the window. It then gets redrawn via calling DrawFrame.
- This means that for an instant part of the ruler is white. If the drawing of the ruler is
- fast, then this is no big deal. If however, your ruler is somewhat complicated and takes
- a while to draw, then you might want to use this second technique.
-
- This second method was also a good way to introduce you to the GWLayers code. Note that
- GWLayers also works for system 6, even if the GWorld calls aren't available. There is
- no system 6 v.s. system 7 compatibility hit if you use GWLayers.
-
-
- Another nice feature of the second sample is that DrawFrame is robust. It doesn't depend
- on the framework setting up things nicely (setting the port, setting the origin to 0,0).
- It handles these details, which allows you to call it from less polite code, such as the
- ScrollFrame procedure for this method.
-
- So, DrawFrame is very robust. How did we really do with the ScrollFrame function?
-
- It would seem hard to do anything wrong with the below code:
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- #pragma unused (dh, dv)
-
- DrawFrame(frHndl, window);
- }
-
- Since DrawFrame is smart, ScrollFrame doesn't have to set things up nicely. So, what's wrong?
- The above code isn't very object-oriented. If your application has only one document type,
- then there's nothing wrong with the above code. However, if you have more than one document
- type, then you should code ScrollFrame so that it uses whatever DrawFrame procedure the document
- has set.
-
- You probably have been wondering how the framework knows what function to call. In effect, it
- doesn't. A pointer to the appropriate function is stored in the frHndl for each of these
- functions. You can change any of these function pointers. Let's say that you create a second
- document type that has a different ruler. Possibly the other document type has a vertical
- ruler, instead of the horizontal ruler we use in this example. This second document would have
- a different pointer for its DrawFrame function. However, it could have the same pointer for
- the ScrollFrame function. Given this possibility, here's what should be done in ScrollFrame:
-
-
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- #pragma unused (dh, dv)
-
- DrawFrameProcPtr proc;
-
- if (proc = (*frHndl)->fileState.drawFrameProc)
- (*proc)(frHndl, window);
- }
-
-
- Unless the DrawFrame procedure pointer is changed, the above code does exactly the same thing.
- When a document is created, the application framework sets the procedure pointers to initial
- values. The initial values are the functions found in the “Window2.c” file. If you have
- only one document type, then you can leave these pointers just as they are and simply drop
- code into the appropriate functions in “Window2.c”, just as we have been doing so far.
- However, if you have multiple document types in your application, you are most likely going
- to want to change some of these procedure pointers.
-
- With this flexibility comes the responsibility of not making direct calls. We need to look
- up the procedure pointer that the document wants used, and then call that procedure.
- An additional feature of the framework is that if the procedure pointer is nil, then that
- particular facility isn't used. This allows you to turn off any of the features of a document
- simply by setting the procedure pointer to nil. So, before you call one of these procedures,
- you first get the procedure pointer, and then if it isn't nil, you call it.
-
-